package handler

import (
	"errors"
	"fmt"
	"github.com/go-git/go-billy/v5/osfs"
	"github.com/go-git/go-git/v5"
	"github.com/go-git/go-git/v5/config"
	"github.com/go-git/go-git/v5/plumbing"
	"github.com/go-git/go-git/v5/plumbing/transport/http"
	"go-devops/common"
	"go-devops/domain/dto"
	"go-devops/domain/model"
	"log"
	"os"
	"path/filepath"
	"strings"
)

type GitTaskHandler struct {
	CommonTaskHandler
}

func NewGitTaskHandler() *GitTaskHandler {
	return &GitTaskHandler{
		CommonTaskHandler: CommonTaskHandler{},
	}
}

type GitData struct {
	Url           string `json:"url"`
	SecretName    string `json:"secretName"`
	DefaultBranch string `json:"defaultBranch"`
	Branch        string `json:"branch"`
}

func getGitData(data interface{}) *GitData {
	jsonString := common.JsonString(data)
	var gitData GitData
	common.ParseJson(jsonString, &gitData)
	return &gitData
}

func (c GitTaskHandler) Stop(context *dto.NodeContext) error {
	context.StopFlag = true
	return nil
}

func (c GitTaskHandler) HandleNode(nodeContext *dto.NodeContext) error {
	return c.Handle(nodeContext, func() error {
		data := getGitData(nodeContext.TaskData.Data)
		workspace := nodeContext.Workspace
		repoPath := getRepoPath(data.Url, workspace)

		sendMsg(nodeContext, true, fmt.Sprintf("工作空间：%s", workspace))
		sendMsg(nodeContext, true, fmt.Sprintf("本地代码地址：%s", repoPath))
		sendMsg(nodeContext, true, fmt.Sprintf("git地址：%s", data.Url))

		secret, err := GetSecret(common.GIT, data.SecretName)
		if err != nil {
			return err
		}
		log.Printf("HandleNode data:%+v secret:+%v\n", data, secret)

		fs := osfs.New(repoPath)
		// 检查目录是否存在，如果不存在则创建
		if _, err := fs.Stat("."); os.IsNotExist(err) {
			os.MkdirAll(repoPath, 0755)
		}

		auth := &http.BasicAuth{
			Username: secret.SecretKey,
			Password: secret.SecretValue,
		}

		r, err := getRepo(repoPath, data.Url, auth, nodeContext)
		if err != nil {
			return err
		}

		w, err := r.Worktree()
		if err != nil {
			return err
		}

		currentBranch, err := getCurrentBranch(r)
		if err != nil {
			return err
		}

		targetBranch := data.Branch
		if len(targetBranch) <= 0 {
			targetBranch = data.DefaultBranch
		}
		sendMsg(nodeContext, true, "执行git分支："+targetBranch)

		if currentBranch != targetBranch {
			log.Printf("GitTaskHandler currentBranch:%s targetBranch:%s", currentBranch, targetBranch)
			// 不是当前分支，切换分支
			checkout(w, r, targetBranch, nodeContext)
		}

		err = pullRepo(w, auth, targetBranch, nodeContext)
		if err != nil {
			return err
		}

		head, err := r.Head()
		if err != nil {
			return err
		}
		log.Printf("git head:%s", head.String())
		sendMsg(nodeContext, true, "代码版本："+head.String())

		commit, err := r.CommitObject(head.Hash())
		if err != nil {
			return err
		}

		log.Printf("git commit:%s", commit.String())
		sendMsg(nodeContext, true, fmt.Sprintf("提交人：%s", commit.Author.Name))
		sendMsg(nodeContext, true, fmt.Sprintf("提交记录：%s", commit.Message))

		nodeContext.Workspace = repoPath

		taskHis := &model.TaskHis{
			TaskId:    nodeContext.TaskId,
			BatchId:   nodeContext.BatchId,
			GitMsg:    commit.Message,
			GitBranch: targetBranch,
		}
		model.UpdateTaskHisGitMsg(taskHis)
		return nil
	})
}

func getRepoPath(url string, workspace string) string {
	lastSlashIndex := strings.LastIndex(url, "/")
	repoNameWithExtension := url[lastSlashIndex+1:]
	repoName := strings.TrimSuffix(repoNameWithExtension, ".git")
	repoPath := filepath.Join(workspace, repoName)
	log.Println("getRepoPath repoPath:", repoPath)
	return repoPath
}

func checkout(w *git.Worktree, r *git.Repository, branch string, nodeContext *dto.NodeContext) error {
	branchCoOpts := git.CheckoutOptions{
		Branch: plumbing.NewBranchReferenceName(branch),
		Force:  true,
	}

	if err := w.Checkout(&branchCoOpts); err != nil {
		mirrorRemoteBranchRefSpec := fmt.Sprintf("refs/heads/%s:refs/heads/%s", branch, branch)
		err = fetchOrigin(r, mirrorRemoteBranchRefSpec, nodeContext)
		if err != nil {
			return err
		}
		err = w.Checkout(&branchCoOpts)
		if err != nil {
			return err
		}
	}
	return nil
}

func getRepo(repoPath string, url string, auth *http.BasicAuth, nodeContext *dto.NodeContext) (*git.Repository, error) {
	r, err := git.PlainOpen(repoPath)
	if err != nil || r == nil {
		r, err = git.PlainCloneContext(nodeContext.Context, repoPath, false, &git.CloneOptions{
			URL:      url,
			Auth:     auth,
			Progress: &CustomWriter{Ctx: nodeContext},
		})
		if err != nil {
			return nil, fmt.Errorf("git clone error:%v", err)
		}
	}
	return r, nil
}

func fetchOrigin(repo *git.Repository, refSpecStr string, nodeContext *dto.NodeContext) error {
	remote, err := repo.Remote("origin")
	if err != nil {
		return fmt.Errorf("git remote error:%v", err)
	}

	var refSpecs []config.RefSpec
	if refSpecStr != "" {
		refSpecs = []config.RefSpec{config.RefSpec(refSpecStr)}
	}

	if err = remote.FetchContext(nodeContext.Context, &git.FetchOptions{
		RefSpecs: refSpecs,
		Progress: &CustomWriter{Ctx: nodeContext},
	}); err != nil {
		if errors.Is(err, git.NoErrAlreadyUpToDate) {
			log.Println("refs already up to date")
		} else {
			return fmt.Errorf("fetch origin failed: %v", err)
		}
	}

	return nil
}

// 获取当前所在分支
func getCurrentBranch(r *git.Repository) (string, error) {
	head, err := r.Head()
	if err != nil {
		return "", fmt.Errorf("获取HEAD失败: %w", err)
	}

	refName := head.Name().Short()
	return refName, nil
}

func pullRepo(w *git.Worktree, auth *http.BasicAuth, branch string, nodeContext *dto.NodeContext) error {
	err := w.PullContext(nodeContext.Context, &git.PullOptions{
		RemoteName:      "origin",
		ReferenceName:   plumbing.NewBranchReferenceName(branch),
		Progress:        &CustomWriter{Ctx: nodeContext},
		Auth:            auth,
		InsecureSkipTLS: true,
	})
	if err != nil && !errors.Is(err, git.NoErrAlreadyUpToDate) {
		return fmt.Errorf("pull origin failed: %v", err)
	}
	return nil
}

func GetBranchList(taskData interface{}, workspace string, taskId string) ([]string, error) {
	data := getGitData(taskData)
	repoPath := getRepoPath(data.Url, workspace)
	r, err := git.PlainOpen(repoPath)
	if err != nil {
		return nil, errors.New("请先初始化项目")
	}

	branches, err := r.Branches()
	if err != nil {
		return nil, err
	}

	var list []string
	branches.ForEach(func(branch *plumbing.Reference) error {
		log.Println("分支：" + branch.Name().Short())
		list = append(list, branch.Name().Short())
		return nil
	})
	return list, nil
}
